﻿using System.Web.Routing;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;

namespace Htse.Blue.Helpers
{
	public static class ExpressionHelperExtension
	{
		public static RouteValueDictionary GetRouteValuesFromExpression<TController>(Expression<Action<TController>> action) where TController : Controller
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}

			MethodCallExpression call = action.Body as MethodCallExpression;
			if (call == null)
			{
				throw new ArgumentException("Must be method call", "action");
			}

			string controllerName = typeof(TController).Name;
			if (!controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
			{
				throw new ArgumentException("Target must end in controller", "action");
			}
			controllerName = controllerName.Substring(0, controllerName.Length - "Controller".Length);
			if (controllerName.Length == 0)
			{
				throw new ArgumentException("Cannot route to controller", "action");
			}

			// TODO: How do we know that this method is even web callable?
			//      For now, we just let the call itself throw an exception.

			var rvd = new RouteValueDictionary();
			rvd.Add("Controller", controllerName);
			rvd.Add("Action", call.Method.Name);
			AddParameterValuesFromExpressionToDictionary(rvd, call);
			return rvd;
		}

		public static string GetInputName<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
		{
			if (expression.Body.NodeType == ExpressionType.Call)
			{
				MethodCallExpression methodCallExpression = (MethodCallExpression)expression.Body;
				string name = GetInputName(methodCallExpression);
				return name.Substring(expression.Parameters[0].Name.Length + 1);

			}
			return expression.Body.ToString().Substring(expression.Parameters[0].Name.Length + 1);
		}

		private static string GetInputName(MethodCallExpression expression)
		{
			// p => p.Foo.Bar().Baz.ToString() => p.Foo OR throw...

			MethodCallExpression methodCallExpression = expression.Object as MethodCallExpression;
			if (methodCallExpression != null)
			{
				return GetInputName(methodCallExpression);
			}
			return expression.Object.ToString();
		}

		static void AddParameterValuesFromExpressionToDictionary(RouteValueDictionary rvd, MethodCallExpression call)
		{
			ParameterInfo[] parameters = call.Method.GetParameters();

			if (parameters.Length > 0)
			{
				for (int i = 0; i < parameters.Length; i++)
				{
					Expression arg = call.Arguments[i];
					object value = null;
					ConstantExpression ce = arg as ConstantExpression;
					if (ce != null)
					{
						// If argument is a constant expression, just get the value
						value = ce.Value;
					}
					else
					{
						// Otherwise, convert the argument subexpression to type object,
						// make a lambda out of it, compile it, and invoke it to get the value
						Expression<Func<object>> lambdaExpression = Expression.Lambda<Func<object>>(Expression.Convert(arg, typeof(object)));
						Func<object> func = lambdaExpression.Compile();
						value = func();
					}
					rvd.Add(parameters[i].Name, value);
				}
			}
		}

	}
}